
import java.util.*;

public class PeerBitmap {

	int numberOfPieces;
	
	//Hashtable storing (peerId, bitmap) for all peers
	Hashtable<Integer, int[]> bitmapHash;
	int [] pieceArray;
	
	//Stores the pieces required by this peer
	ArrayList<Integer> requiredPieces;
	
	public PeerBitmap(int numberOfPieces)
	{
		this.numberOfPieces = numberOfPieces;
		this.pieceArray = new int[numberOfPieces];
		this.bitmapHash = new Hashtable<Integer, int[]>();
		this.requiredPieces = new ArrayList<Integer>();
		
		//Initializing the bitmap array to zero
		for(int j=0; j < numberOfPieces; j++)
			this.pieceArray[j] = 0;
	}
	
	//Piece Number to be entered each time piece is downloaded or recieved from 'have'
	//In the beginning, enter '-1' to indicate the entire file and '-2' to indicate 
	//the peer does not have the file
	public void setBitmap(int peerId, int pieceNumber)
	{
		if(pieceNumber == -1 || pieceNumber == -2)
		{
			int[] bitmapArray = new int[numberOfPieces];
			
			if(pieceNumber == -1)
				for(int i=0; i < numberOfPieces; i++)
					bitmapArray[i] = 1;
			else
				for(int i=0; i < numberOfPieces; i++)
					bitmapArray[i] = 0;
			
			bitmapHash.put(new Integer(peerId), bitmapArray);
			
		}
		else
			bitmapHash.get(new Integer(peerId))[pieceNumber] = 1;
		
		if(peerId == PeerProcess.ThisPeerId)
			updateRequiredPieces();
	}
	
	public void setBitmap(int peerId, int pieceNumber, int value)
	{
		bitmapHash.get(new Integer(peerId))[pieceNumber] = value;
		
		if(peerId == PeerProcess.ThisPeerId)
		{
			if(this.requiredPieces.contains(new Integer(pieceNumber)) && value == 1)
				this.requiredPieces.remove(new Integer(pieceNumber));
			else if(!this.requiredPieces.contains(new Integer(pieceNumber)) && value == 0)
				this.requiredPieces.add(new Integer(pieceNumber));
		}
		//PeerProcess.logEntry("Setbitmap of "+peerId+"'s piece "+pieceNumber+"to "+value);
		//this.updateRequiredPieces();
	}
	
	//To indicate if the peer has requested the piece
	//PeerNumber will be the current peer Id
	public void setRequestedPiece(int pieceNumber)
	{
		bitmapHash.get(new Integer(PeerProcess.ThisPeerId))[pieceNumber] = 2;
		
		//this.updateRequiredPieces();
		if(this.requiredPieces.contains(new Integer(pieceNumber)))
			this.requiredPieces.remove(new Integer(pieceNumber));
	}
	
	//Check if the peer has a piece
	public boolean peerHasPiece(int peerId, int pieceNumber)
	{
		boolean result;

		if(bitmapHash.get(new Integer(peerId))[pieceNumber] == 1)
			result = true;
		else
			result = false;

		//PeerProcess.logEntry("checked if "+peerId+" has piece "+pieceNumber+" and result = "+result);
		return result;
	}
	
	//Returns all the peers which have the particular piece
	//Returns with -1 if no peers have the piece
	public int[] peersWithPiece(int pieceNumber)
	{
		int[] peerList = new int[bitmapHash.size()];
		int peerCount = 0;
		
		for(Enumeration<Integer> mapKeys = bitmapHash.keys(); mapKeys.hasMoreElements();)
		{
			int key = (int)mapKeys.nextElement();
			if(bitmapHash.get(new Integer(key))[pieceNumber] == 1)
				peerList[peerCount++] = key;
		}
		
		if(peerCount > 0)
			return peerList;
		
		peerList[0] = -1;
		return peerList;				
	}
	
	//Converts the bitmap into a byteArray so that
	//it can be sent as a payload to another peer
	public byte[] getByteArray(int peerId)
	{
		int[] pieceArray = bitmapHash.get(new Integer(peerId));
		
		int size = (int)Math.ceil(pieceArray.length / 8.0);
		
		int remainder = pieceArray.length % 8;
		//System.out.println("Remainder = "+remainder+" Size = "+size);
		
		byte[] byteArray = new byte[size];
		
	    for (int i = 0, j = 0; j < pieceArray.length; i++, j += 8)
	    {
	        for (int offset = 0; offset < 8; offset++)
	        {
	        	if(i != size - 1)
	        	{
	        		if(pieceArray[j + offset] == 2)
	        			byteArray[i] |= (0 << (7-offset));
	        		else
	        			byteArray[i] |= (pieceArray[j + offset] << (7-offset));
	        	
	        		//System.out.println(pieceArray[j+offset]);
	        	}
	        	else if(i == size - 1 && offset < remainder)
	        		if(pieceArray[j + offset] == 2)
	        			byteArray[i] |= (0 << (7-offset));
	        		else
	        			byteArray[i] |= (pieceArray[j + offset] << (7-offset));
	        	
	        }
	        
	        //System.out.println(" Byte = "+byteArray[i]);
	    }

	    return byteArray;
	}
	
	//Converts the incoming byte array bitmap into integer and
	//setting the bitmap for the corresponding peer
	public void setBitmap(int peerId, byte[] byteArray)
	{
		int[] pieceArray = bitmapHash.get(new Integer(peerId));
		
		for(int i = 0; i < byteArray.length; i++)
		{
			for(int j = 0; j < 8 && (8*i + j) < pieceArray.length; j++)
			{
				pieceArray[i*8+j] = (byteArray[i]>>(7-j)) & 1;
			}
		}
		
		//for(int i = 0; i < pieceArray.length; i++)
		//	System.out.println(pieceArray[i]);
		
		if(peerId == PeerProcess.ThisPeerId)
			this.updateRequiredPieces();
	}
	
	//Checks if the peer has any interesting peers
	public boolean checkIfInterested(int peerId)
	{
		if(this.requiredPieces.size() == 0)
			return false;
		
		for(int i = 0; i < this.requiredPieces.size(); i++)
			if(this.peerHasPiece(peerId, (int)this.requiredPieces.get(i)))
				return true;
		
		return false;
	}
	
	//Updates the pieces needed by this peer
	public void updateRequiredPieces()
	{
		int[] thisPeerBitmap = bitmapHash.get(new Integer(PeerProcess.ThisPeerId));
		
		this.requiredPieces.clear();
		
		for(int i = 0; i < thisPeerBitmap.length; i++)
			if(thisPeerBitmap[i] == 0)
				this.requiredPieces.add(new Integer(i));
	}
	
	//Gets a piece to request
	public int getPiecetoRequest(int peerId)
	{
		//Stores the index numbers of the interesting pieces of the other peer
		ArrayList<Integer> interestedPieces = new ArrayList<Integer>();
		int[] otherPeerBitmap = bitmapHash.get(new Integer(peerId));
		
		for(int i=0; i < this.requiredPieces.size(); i++)
		{
			int index = this.requiredPieces.get(i);
			
			if(otherPeerBitmap[index] == 1)
				interestedPieces.add(new Integer(index));
		}
		
		//Chooses a random piece from the range [0, size of interestedPieces)
		//Obtains the actual piece number
		Random randomInt = new Random();
		int idx = randomInt.nextInt(interestedPieces.size());
		int requestPiece = interestedPieces.get(idx);
		
		//Modifies this peer's bitmap - Sets the piece value to requested
		this.setRequestedPiece(requestPiece);
		
		return requestPiece;		
	}
	
	//Checks if the peer has the all the pieces
	public boolean checkIfAllPieces(int peerId)
	{
		int[] pieceArray = bitmapHash.get(new Integer(peerId));
		
		for(int i = 0; i < pieceArray.length; i++)
			if(pieceArray[i] != 1)
				return false;
		
		return true;
	}
	
	//Checks if the peer has the all the pieces
	public int returnNumberOfPieces()
	{
		int[] pieceArray = bitmapHash.get(new Integer(PeerProcess.ThisPeerId));
		int sum = 0;
		
		for(int i = 0; i < pieceArray.length; i++)
			if(pieceArray[i] == 1)
				sum++;
		
		return sum;
	}

	//Checks if the piece is requested or not
	public boolean checkIfPieceRequested(int peerId, int pieceNumber)
	{
		 boolean result;
                
                if(bitmapHash.get(new Integer(peerId))[pieceNumber] == 2)
                        result = true;
                else
                        result = false;
                        
                //PeerProcess.logEntry("checked if "+peerId+" has piece "+pieceNumber+" and result = "+result);

                return result;

	}
}
